//
//  LBWebView.m
//  LBWebView
//
//  Created by WuShiHai on 19/5/24.
//  Copyright (c) 2019 Lebbay. All rights reserved.
//

#import "LBWebView.h"
#import <LBFoundation/LBFoundation.h>

#define LBWebViewBlockSafeRun(block, ...) block ? block(__VA_ARGS__) : nil

@implementation LBWebViewBridge
@end

@interface LBScriptMessageHandler : NSObject<WKScriptMessageHandler>

@property (nonatomic ,weak) id<WKScriptMessageHandler> delegate;

@end

@interface NSURL (LBWebViewAddition)
//serviceName=PERSONAL_REGISTER&platformNo=3000000028

@end

@implementation NSURL (LBWebViewAddition)

- (BOOL)safeURL{
    return self.scheme.length > 0
    && self.resourceSpecifier.length > 0
    && !self.fileURL
    && [self.scheme hasPrefix:@"http"];
}

@end

@interface LBWebView()<WKNavigationDelegate,WKUIDelegate,WKScriptMessageHandler>

@property (nonatomic ,weak,) id<LBWebViewDelegate> lb_webViewDelegate;

@property (nonatomic, strong) NSUserActivity *userActivity;
@property (nonatomic, strong) LBScriptMessageHandler *handler;

//WKWebView POST Body
@property (nonatomic, strong, nullable) NSString *HTTPBody;
@property (nonatomic, assign) BOOL didPOSTHTTPBody;

@end

@implementation LBWebView

- (void)dealloc{
    
}

- (instancetype)initWithFrame:(CGRect)frame
                     delegate:(id<LBWebViewDelegate>)delegate{
    
    WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
    WKUserContentController* userContent = [[WKUserContentController alloc] init];
    // self 指代的对象需要遵守 WKScriptMessageHandler 协议
    LBScriptMessageHandler *handler = [[LBScriptMessageHandler alloc] init];
    handler.delegate = self;
    NSString *scriptMessageName = @"lebbay";//LBBundleConfigureStringValue(LBWebViewServiceConfiguration, LBWebViewServiceScriptMessageName, @"LBWebView");
    [userContent addScriptMessageHandler:handler
                                    name:scriptMessageName];
    configuration.userContentController = userContent;
    
    if (@available(iOS 9.0, *)) {
        // do nothing        
        configuration.applicationNameForUserAgent = [self userAgent];
    }
    
    if (self = [super initWithFrame:frame configuration:configuration]) {
        _lb_webViewDelegate = delegate;
        [self configureView];
    }
    
    return self;
}

- (NSString *)userAgent {
    NSString *projectName = LBBundleConfigureStringValue(LBWebViewServiceConfiguration, LBWebViewServiceProjectName, @"LebbayApp");
    
    NSString *appVersion = [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey];
    NSString *userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@/%@; Scale/%0.2f)", projectName, [[UIDevice currentDevice] systemVersion], [[UIDevice currentDevice] model], AppBundleIdentifier(), appVersion, [[UIScreen mainScreen] scale]];
    return userAgent;
}

- (void)configureView{
    self.navigationDelegate = self;
    self.UIDelegate = self;
    if (@available(iOS 8.0, *)) {
        // do nothing
    } else {
        self.allowsBackForwardNavigationGestures = YES;
    }
    
    _userActivity = [[NSUserActivity alloc] initWithActivityType:@"ml"];
    _userActivity.title = @"萌小贷WebView";
}

- (nullable WKNavigation *)loadRequest:(NSURLRequest *)request{
    
    if ([request.URL safeURL]) {
        _userActivity.webpageURL = [request.URL copy];
        [_userActivity becomeCurrent];
    }
    
    if ([self isNeedPost]) {
        return [self addPOSTHTTPBodyScript:request.URL];
    }else{
        return [super loadRequest:request];
    }
}

- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id _Nullable, NSError * _Nullable))completionHandler{
    id strongSelf = self;
    [super evaluateJavaScript:javaScriptString completionHandler:^(id _Nullable obj, NSError * _Nullable error) {
        
        //import
        [strongSelf description];
        //目前不需要特殊处理
        //3.0
        completionHandler?completionHandler(obj,error):nil;
    }];
}

#pragma mark - 解决WKWebView 无法POST body问题
- (BOOL)isNeedPost{
    return self.HTTPBody.length > 0;
}
- (WKNavigation *)addPOSTHTTPBodyScript:(NSURL *)baseURL{
    
    NSString *path = [[NSBundle mainBundle] pathForResource:@"JSPOST" ofType:@"html"];
    // 获得html内容
    NSString *html = [[NSString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    // 加载js
    return [self loadHTMLString:html baseURL:[[NSBundle mainBundle] bundleURL]];
}
- (void)POSTHTTPBodyByScript{
    
    NSArray *paramStrings = [self.HTTPBody componentsSeparatedByString:@"&"];
    NSMutableDictionary *params = [@{} mutableCopy];
    [paramStrings enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSArray *values = [obj componentsSeparatedByString:@"="];
        if (values.count == 2) {
            NSString *value = [values objectAtIndex:1];
            [params setObject:value.stringByRemovingPercentEncoding forKey:[values firstObject]];
        }
    }];
    NSString *postData = [[params yy_modelToJSONString] stringByReplacingOccurrencesOfString:@"\n" withString:@""];
    // 请求的页面地址
    NSString *urlStr = self.URL.absoluteString;
    
    // 拼装成调用JavaScript的字符串
    //调用格式： MD_POSTHTTPBody('URL', {"key": "value"});
    NSString *jscript = [NSString stringWithFormat:@"MD_POSTHTTPBody('%@', %@);", urlStr, postData];
    
    [self evaluateJavaScript:jscript completionHandler:^(id _Nullable obj, NSError * _Nullable error) {
        
    }];
}

#pragma mark - WKScriptMessageHandler
/**
 *  JS 调用 OC 时 webview 会调用此方法
 *
 *  @param userContentController  webview 中配置的 userContentController 信息
 *  @param message                js 执行传递的消息
 */
- (void)userContentController:(WKUserContentController*)userContentController didReceiveScriptMessage:(WKScriptMessage*)message
{
    NSLog(@"%@", message.body);
    
    if ([_lb_webViewDelegate respondsToSelector:@selector(LBWebView:didReceiveMessage:)]) {
        if ([message.body isKindOfClass:[NSString class]]) {
            LBWebViewBridge *messageBridge = [LBWebViewBridge yy_modelWithJSON:message.body];
            if (messageBridge) {
                [_lb_webViewDelegate LBWebView:self didReceiveMessage:messageBridge];
            }
        }
    }
}

#pragma mark - WKNavigationDelegate

//- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(nonnull void (^)(void))completionHandler{
//
//}
//
//- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler {
//
//
//}

//- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler {
//    completionHandler(defaultText);
//}

/*! @abstract Decides whether to allow or cancel a navigation.
 @param webView The web view invoking the delegate method.
 @param navigationAction Descriptive information about the action
 triggering the navigation request.
 @param decisionHandler The decision handler to call to allow or cancel the
 navigation. The argument is one of the constants of the enumerated type WKNavigationActionPolicy.
 @discussion If you do not implement this method, the web view will load the request or, if appropriate, forward it to another application.
 */
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    
    //
    BOOL isAction = YES;
    
    if (isAction &&[self.lb_webViewDelegate respondsToSelector:@selector(LBWebView:shouldStartLoadWithRequest:)]) {
        isAction = [self.lb_webViewDelegate LBWebView:self shouldStartLoadWithRequest:navigationAction.request];
    }
    
    //如果业务层未处理，则这里通用库进行跳转处理
    if (isAction) {
        NSURL *URL = navigationAction.request.URL;
        //允许全部打开
        //        if ([[LBWebView wd_whiteScheme] containsObject:URL.scheme]) {
        //非HTTP和HTTPS协议，可以打开外部
        if ([@"http" isEqualToString:URL.scheme] == NO && [@"https" isEqualToString:URL.scheme] == NO) {
            [[UIApplication sharedApplication] openURL:URL];
        }
        //        }
    }
    
    LBWebViewBlockSafeRun(decisionHandler,isAction?WKNavigationActionPolicyAllow:WKNavigationActionPolicyCancel);
}


/*! @abstract Decides whether to allow or cancel a navigation after its
 response is known.
 @param webView The web view invoking the delegate method.
 @param navigationResponse Descriptive information about the navigation
 response.
 @param decisionHandler The decision handler to call to allow or cancel the
 navigation. The argument is one of the constants of the enumerated type WKNavigationResponsePolicy.
 @discussion If you do not implement this method, the web view will allow the response, if the web view can show it.
 */
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
    LBWebViewBlockSafeRun(decisionHandler,WKNavigationResponsePolicyAllow);
}

/*! @abstract Invoked when a main frame navigation starts.
 @param webView The web view invoking the delegate method.
 @param navigation The navigation.
 */
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation{
    
    if ([self.lb_webViewDelegate respondsToSelector:@selector(LBWebViewDidStartLoad:)]) {
        [self.lb_webViewDelegate LBWebViewDidStartLoad:self];
    }
}

/*! @abstract Invoked when a server redirect is received for the main
 frame.
 @param webView The web view invoking the delegate method.
 @param navigation The navigation.
 */
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation{
    
}

/*! @abstract Invoked when an error occurs while starting to load data for
 the main frame.
 @param webView The web view invoking the delegate method.
 @param navigation The navigation.
 @param error The error that occurred.
 */
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error{
    if ([self.lb_webViewDelegate respondsToSelector:@selector(LBWebView:didFailLoadWithError:)]) {
        [self.lb_webViewDelegate LBWebView:self didFailLoadWithError:error];
    }
}

/*! @abstract Invoked when content starts arriving for the main frame.
 @param webView The web view invoking the delegate method.
 @param navigation The navigation.
 */
- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation{
    
}

/*! @abstract Invoked when a main frame navigation completes.
 @param webView The web view invoking the delegate method.
 @param navigation The navigation.
 */
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{
    
    if ([self isNeedPost] && _didPOSTHTTPBody == NO) {
        _didPOSTHTTPBody = YES;
        [self POSTHTTPBodyByScript];
        return;
    }
    
    [self evaluateJavaScript:@"document.documentElement.style.webkitTouchCallout='none';" completionHandler:nil];
    [self evaluateJavaScript:@"document.documentElement.style.webkitUserSelect='none';" completionHandler:nil];
    
    if ([self.lb_webViewDelegate respondsToSelector:@selector(LBWebViewDidFinishLoad:)]) {
        [self.lb_webViewDelegate LBWebViewDidFinishLoad:self];
    }
    
}

/*! @abstract Invoked when an error occurs during a committed main frame
 navigation.
 @param webView The web view invoking the delegate method.
 @param navigation The navigation.
 @param error The error that occurred.
 */
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error{
    
    if ([self.lb_webViewDelegate respondsToSelector:@selector(LBWebView:didFailLoadWithError:)]) {
        [self.lb_webViewDelegate LBWebView:self didFailLoadWithError:error];
    }
    
}

/*! @abstract Invoked when the web view's web content process is terminated.
 @param webView The web view whose underlying web content process was terminated.
 */
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView{
    
    if ([self.lb_webViewDelegate respondsToSelector:@selector(LBWebView:didFailLoadWithError:)]) {
        [self.lb_webViewDelegate LBWebView:self didFailLoadWithError:[[NSError alloc] initWithDomain:@"webViewWebContentProcessDidTerminate" code:0 userInfo:nil]];
    }
}

/// webview 收到身份验证挑战
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
    
    NSString *authenticationMethod = [[challenge protectionSpace] authenticationMethod];
    if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPBasic] && ([challenge.protectionSpace.host containsString:@"azazie"] || [challenge.protectionSpace.host containsString:@"blushmark"])) {
        NSURLCredential *credential = [NSURLCredential credentialWithUser:@"lebbay" password:@"passw0rd" persistence:NSURLCredentialPersistenceForSession];
        completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
    } else {
        completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
    }
}
//test alert();
//- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
//{
//    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:message
//                                                                             message:nil
//                                                                      preferredStyle:UIAlertControllerStyleAlert];
//    [alertController addAction:[UIAlertAction actionWithTitle:@"OK"
//                                                        style:UIAlertActionStyleCancel
//                                                      handler:^(UIAlertAction *action) {
//                                                          completionHandler();
//                                                      }]];
//    [self.native.webViewControllerOwner presentViewController:alertController animated:YES completion:^{}];
//}

#pragma mark - Private Method
+ (NSArray *)wd_whiteScheme {
    static NSArray *schemes;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        schemes = @[
                    @"sinaweibo",
                    @"sinaweibohd",
                    @"sinaweibosso",
                    @"sinaweibohdsso",
                    @"weibosdk",
                    @"weibosdk2.5",
                    @"wechat",
                    @"weixin",
                    @"TencentWeibo",
                    @"tencentweiboSdkv2",
                    @"mqzoneopensdk",
                    @"mqzoneopensdkapi",
                    @"mqzoneopensdkapi19",
                    @"mqzoneopensdkapiV2",
                    @"mqqOpensdkSSoLogin",
                    @"mqqopensdkapiV2",
                    @"mqqopensdkapiV3",
                    @"wtloginmqq2",
                    @"mqqapi",
                    @"mqqwpa",
                    @"mqzone",
                    @"mqq",
                    @"alipay",
                    @"alipays",
                    ];
    });
    return schemes;
}

@end

@implementation LBScriptMessageHandler

- (void)userContentController:(WKUserContentController*)userContentController didReceiveScriptMessage:(WKScriptMessage*)message
{
    if ([self.delegate respondsToSelector:@selector(userContentController:didReceiveScriptMessage:)]) {
        [self.delegate userContentController:userContentController didReceiveScriptMessage:message];
    }
}

@end
